﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using NClass.Core;
using NClass.TestHelper;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
namespace NClass.Test.Core.Entities
{
	[TestClass]
	public class CompositeTypeTest
	{
		private class OrdinalTestSet
		{
			internal OrdinalTestSet(Member m, Action<Member> i, int e)
			{
				Member = m;
				InitializeMember = i;
				ExpectedOrdinal = e;
			}
			internal Member Member { get; private set; }
			internal Action<Member> InitializeMember { get; private set; }
			internal int ExpectedOrdinal { get; private set; }
		}

		[TestMethod]
		[TestCategoryAttribute("Branch Path Coverage")]
		public void GetMemberOrdinal()
		{
			var testClass = Resolver.CreateClass(Resolver.Language.CSharp);

			var dataSets = new List<OrdinalTestSet>();
			// Constant Field - 0
			dataSets.Add(new OrdinalTestSet(testClass.AddField(), (member) =>
			{
				((Field)member).InitialValue = "1";
				((Field)member).IsConstant = true;
			}, 0));
			// Non-Constant Field - 1
			dataSets.Add(new OrdinalTestSet(testClass.AddField(), null, 1));
			// Read-Only Property - 2
			dataSets.Add(new OrdinalTestSet(testClass.AddProperty(), (member) => { ((Property)member).IsReadonly = true; }, 2));
			// Write Only Property - 3
			dataSets.Add(new OrdinalTestSet(testClass.AddProperty(), (member) => { ((Property)member).IsWriteonly = true; }, 3));
			// Basic Property - 4
			dataSets.Add(new OrdinalTestSet(testClass.AddProperty(), null, 4));
			// Constructor - 5;
			dataSets.Add(new OrdinalTestSet(testClass.AddConstructor(), null, 5));
			// Method Operator - 6
			dataSets.Add(new OrdinalTestSet(testClass.AddMethod(), (member) =>
			{
				// Set name to get the method to become an operator
				member.Name = "implicit operator TestClass";
			}, 6));
			// Destructor - 8
			dataSets.Add(new OrdinalTestSet(testClass.AddDestructor(), null, 8));
			// Method - 7
			dataSets.Add(new OrdinalTestSet(testClass.AddMethod(), null, 7));
			// Event - 9
			dataSets.Add(new OrdinalTestSet(testClass.AddEvent(), null, 9));
			// Unknown Case - 10
			dataSets.Add(new OrdinalTestSet(null, null, 10));

			var methodInfo = (typeof(CompositeType)).GetMethod("GetMemberOrdinal",
				BindingFlags.NonPublic | BindingFlags.Static);

			foreach (var dataSet in dataSets)
			{
				try
				{
					if (dataSet.InitializeMember != null)
						dataSet.InitializeMember(dataSet.Member);

					var result = methodInfo.Invoke(null, new object[] { dataSet.Member }) as int?;

					if (!result.HasValue)
						Assert.Fail("No value was returned from the CompositeType.GetMemberOrdinal() function");

					Assert.AreEqual(dataSet.ExpectedOrdinal, result.Value);
				}
				catch (Exception ex)
				{
					Assert.Fail(ex.Message);
				}
			}
		}

		private class InsertMemberTestSet
		{
			internal InsertMemberTestSet(CompositeType c, MemberType m, int i, int e)
			{
				Instance = c;
				MemberType = m;
				InsertIndex = i;
				ExpectedIndex = e;
			}

			internal CompositeType Instance { get; private set; }
			internal MemberType MemberType { get; private set; }
			internal int InsertIndex { get; set; }
			internal virtual int ExpectedIndex { get; set; }
		}

		private class InsertMemberTestSetException : InsertMemberTestSet
		{
			internal InsertMemberTestSetException(CompositeType c, MemberType m, int i, Type e) :
				base(c, m, i, -1)
			{
				ExceptionType = e;
			}

			internal Type ExceptionType { get; set; }
		}

		private class InsertMemberRandomTestSet : InsertMemberTestSetException
		{
			internal InsertMemberRandomTestSet(CompositeType c) :
				base(c, GetRandomMemberType(), GetRandomInsertIndex(), null)
			{
				ExceptionType = GetExpectedExceptionType(Instance, MemberType);
			}

			internal override int ExpectedIndex
			{
				get { return GetExpectedIndex(); }
				set { }
			}

			private static MemberType GetRandomMemberType()
			{
				var rnd = (new Random()).Next(5);

				if (rnd % 5 == 0)
					return MemberType.Field;

				rnd = (new Random(rnd)).Next(5);

				switch (rnd)
				{
					case 0:
						return MemberType.Constructor;
					case 1:
						return MemberType.Destructor;
					case 2:
						return MemberType.Event;
					case 3:
						return MemberType.Method;
					case 4:
						return MemberType.Property;
				}

				return MemberType.Field;
			}

			private static int GetRandomInsertIndex()
			{
				// Random Number between 0 and Int32.Max
				var r1 = new Random();
				var r2 = new Random(r1.Next(500));
				return r2.Next(Int32.MaxValue);
			}

			private int GetExpectedIndex()
			{
				var m = MemberType;
				var c = Instance;
				var i = InsertIndex;

				if (m == MemberType.Field)
				{
					return c.FieldCount <= i ? c.FieldCount - 1 : i;
				}

				i -= c.FieldCount;

				if (i <= 0)
					return 0;
				return c.OperationCount <= i ? c.OperationCount - 1 : i;
			}

			private static Type GetExpectedExceptionType(CompositeType c, MemberType m)
			{
				if (c is ClassType)
					return null;
				if (c is StructureType)
				{
					if (m != MemberType.Destructor)
						return null;
				}
				else if (m != MemberType.Field && m != MemberType.Constructor && m != MemberType.Destructor)
					return null;


				return typeof(InvalidOperationException);
			}
		}

		[TestMethod]
		[TestCategoryAttribute("Domain Test")]
		public void InsertMemberRandomInputs()
		{
			Language language = null;
			Model model = null;

			var ctClass = Resolver.CreateClass(Resolver.Language.CSharp, out language, out model);
			var ctInterface = model.AddInterface();
			var ctStruct = model.AddStructure();

			var dataSets = new List<InsertMemberTestSet>();

			// Generate Randomized Tests
			for (int ii = 0; ii < 10000; ++ii)
			{
				var rnd = new Random(ii);
				var pick = rnd.Next(3);
				if (pick == 0)
					dataSets.Add(new InsertMemberRandomTestSet(ctClass));
				else if (pick == 1)
					dataSets.Add(new InsertMemberRandomTestSet(ctInterface));
				else
					dataSets.Add(new InsertMemberRandomTestSet(ctStruct));
			}

			foreach (var dataSet in dataSets)
			{
				try
				{
					dataSet.Instance.InsertMember(dataSet.MemberType, dataSet.InsertIndex);

					if (dataSet.MemberType == MemberType.Field)
					{
						var field = dataSet.Instance.GetField(dataSet.ExpectedIndex);
						if ("newField" != field.Name)
						{

						}
						Assert.AreEqual("newField", field.Name);
						field.Name = "Name";
					}
					else
					{
						string expectedName = "";

						if (dataSet.MemberType == MemberType.Constructor)
							expectedName = dataSet.Instance.Name;
						else if (dataSet.MemberType == MemberType.Destructor)
							expectedName = "~" + dataSet.Instance.Name;
						else if (dataSet.MemberType == MemberType.Event)
							expectedName = "NewEvent";
						else if (dataSet.MemberType == MemberType.Property)
							expectedName = "NewProperty";
						else
							expectedName = "NewMethod";

						var operation = dataSet.Instance.GetOperation(dataSet.ExpectedIndex);
						if (expectedName != operation.Name)
						{

						}
						Assert.AreEqual(expectedName, operation.Name);

						if (dataSet.MemberType != MemberType.Constructor &&
							dataSet.MemberType != MemberType.Destructor)
						{
							operation.Name = "Name";
						}
					}
				}
				catch (Exception ex)
				{
					var exceptionSet = dataSet as InsertMemberTestSetException;
					if (exceptionSet == null || exceptionSet.ExceptionType == null || exceptionSet.ExceptionType != ex.GetType())
					{
						Assert.Fail(ex.GetType().Name + ": " + ex.Message);
					}
				}
			}
		}

		[TestMethod]
		[TestCategoryAttribute("Branch Path Coverage")]
		public void InsertMember()
		{
			Language language = null;
			Model model = null;

			var ctClass = Resolver.CreateClass(Resolver.Language.CSharp, out language, out model);
			var ctInterface = model.AddInterface();
			var ctStruct = model.AddStructure();

			var dataSets = new List<InsertMemberTestSet>();
			// Interface
			dataSets.Add(new InsertMemberTestSetException(ctInterface, MemberType.Field, 100, typeof(InvalidOperationException)));

			dataSets.Add(new InsertMemberTestSet(ctInterface, MemberType.Method, 100, 0));
			dataSets.Add(new InsertMemberTestSet(ctInterface, MemberType.Method, 100, 1));

			dataSets.Add(new InsertMemberTestSetException(ctInterface, MemberType.Constructor, 0, typeof(InvalidOperationException)));
			dataSets.Add(new InsertMemberTestSetException(ctInterface, MemberType.Destructor, 3, typeof(InvalidOperationException)));
			dataSets.Add(new InsertMemberTestSet(ctInterface, MemberType.Event, 9, 2));
			dataSets.Add(new InsertMemberTestSet(ctInterface, MemberType.Property, 10, 3));

			// Struct
			dataSets.Add(new InsertMemberTestSet(ctStruct, MemberType.Field, 100, 0));
			dataSets.Add(new InsertMemberTestSet(ctStruct, MemberType.Field, 0, 0));
			dataSets.Add(new InsertMemberTestSet(ctStruct, MemberType.Field, 100, 2));
			dataSets.Add(new InsertMemberTestSet(ctStruct, MemberType.Field, -1, 0));

			dataSets.Add(new InsertMemberTestSet(ctStruct, MemberType.Method, 100, 0));
			dataSets.Add(new InsertMemberTestSet(ctStruct, MemberType.Method, 100, 1));

			dataSets.Add(new InsertMemberTestSet(ctStruct, MemberType.Constructor, 0, 0));
			dataSets.Add(new InsertMemberTestSetException(ctStruct, MemberType.Destructor, 3, typeof(InvalidOperationException)));
			dataSets.Add(new InsertMemberTestSet(ctStruct, MemberType.Event, 9, 3));
			dataSets.Add(new InsertMemberTestSet(ctStruct, MemberType.Property, 10, 4));

			// Class
			dataSets.Add(new InsertMemberTestSet(ctClass, MemberType.Field, 100, 0));
			dataSets.Add(new InsertMemberTestSet(ctClass, MemberType.Field, 0, 0));
			dataSets.Add(new InsertMemberTestSet(ctClass, MemberType.Field, 100, 2));
			dataSets.Add(new InsertMemberTestSet(ctClass, MemberType.Field, -1, 0));

			dataSets.Add(new InsertMemberTestSet(ctClass, MemberType.Method, 100, 0));
			dataSets.Add(new InsertMemberTestSet(ctClass, MemberType.Method, 100, 1));

			dataSets.Add(new InsertMemberTestSet(ctClass, MemberType.Constructor, 0, 0));
			dataSets.Add(new InsertMemberTestSet(ctClass, MemberType.Destructor, 3, 0));
			dataSets.Add(new InsertMemberTestSet(ctClass, MemberType.Event, 9, 4));
            //dataSets.Add(new InsertMemberTestSet(ctClass, MemberType.Event, 9, 3));
			dataSets.Add(new InsertMemberTestSet(ctClass, MemberType.Property, 10, 5));

			foreach (var dataSet in dataSets)
			{
				try
				{
					dataSet.Instance.InsertMember(dataSet.MemberType, dataSet.InsertIndex);

					if (dataSet.MemberType == MemberType.Field)
					{
						var field = dataSet.Instance.GetField(dataSet.ExpectedIndex);
						Assert.AreEqual("newField", field.Name);
						field.Name = "Name";
					}
					else
					{
						string expectedName = "";

						if (dataSet.MemberType == MemberType.Constructor)
							expectedName = dataSet.Instance.Name;
						else if (dataSet.MemberType == MemberType.Destructor)
							expectedName = "~" + dataSet.Instance.Name;
						else if (dataSet.MemberType == MemberType.Event)
							expectedName = "NewEvent";
						else if (dataSet.MemberType == MemberType.Property)
							expectedName = "NewProperty";
						else
							expectedName = "NewMethod";

						var operation = dataSet.Instance.GetOperation(dataSet.ExpectedIndex);
						Assert.AreEqual(expectedName, operation.Name);

						if (dataSet.MemberType != MemberType.Constructor &&
							dataSet.MemberType != MemberType.Destructor)
						{
							operation.Name = "Name";
						}
					}
				}
				catch (Exception ex)
				{
					var exceptionSet = dataSet as InsertMemberTestSetException;
					if (exceptionSet == null || exceptionSet.ExceptionType == null || exceptionSet.ExceptionType != ex.GetType())
					{
						Assert.Fail(ex.GetType().Name + ": " + ex.Message);
					}
				}
			}
		}

        [TestMethod]
        [TestCategoryAttribute("Mutation Killer test")]
        public void InsertMemberMutation()
        {
            Model model = null;

            var classes = Resolver.CreateClasses(Resolver.Language.CSharp, out model, 
                "EventClass", "ConstructorClass", "DestructorClass", "PropertyClass");
            
            try
            {
                //Test adding Events
                classes["EventClass"].InsertMember(MemberType.Method, 0);
                classes["EventClass"].InsertMember(MemberType.Event, 0);
                int eventCount = classes["EventClass"].Operations.OfType<Event>().Count();
                Assert.AreEqual(eventCount, 1);

                classes["ConstructorClass"].InsertMember(MemberType.Method, 0);
                classes["ConstructorClass"].InsertMember(MemberType.Constructor, 0);
                int constructorCount = classes["ConstructorClass"].Operations.OfType<Constructor>().Count();
                Assert.AreEqual(constructorCount, 1);

                classes["DestructorClass"].InsertMember(MemberType.Method, 0);
                classes["DestructorClass"].InsertMember(MemberType.Destructor, 0);
                int desctructorCount = classes["DestructorClass"].Operations.OfType<Destructor>().Count();
                Assert.AreEqual(desctructorCount, 1);

                classes["PropertyClass"].InsertMember(MemberType.Method, 0);
                classes["PropertyClass"].InsertMember(MemberType.Property, 0);
                int propertyCount = classes["PropertyClass"].Operations.OfType<Property>().Count();
                Assert.AreEqual(propertyCount, 1);
            }
            catch (Exception ex)
            {
               
                Assert.Fail(ex.GetType().Name + ": " + ex.Message);
                
            }
            
        }

		[TestMethod]
		[TestCategoryAttribute("Load Test")]
		public void AddFieldLoadTest()
		{
			var testClass = TestHelper.Resolver.CreateClass(Resolver.Language.CSharp);

			TestHelper.Timer.TimedExecution(60000, () =>
			{
				for (long i = 0; i <= int.MaxValue; i++)
				{
					var field = testClass.AddField();
					field.Name = "Field" + i.ToString();
					field.Type = "string";
				}
			});
			var count = testClass.Fields.Count();
			Assert.IsTrue(count >= 90000, "Fields Added in 60s: " + count.ToString());
		}

	}
}
